package ast

import (
	"fmt"
	"strings"
)

// ExprType is the type enumeration.
type ExprType string

// Expr is the interface of all expression types.
type Expr interface {
	Type() ExprType
	String() string
}

// ExprTypeOperator is the operator expression type.
const ExprTypeOperator ExprType = "OperatorExpr"

// OperatorType is the operator type.
type OperatorType int

// predefined operator types.
const (
	OpTypeAdd OperatorType = iota
	OpTypeSub
	OpTypeMul
	OpTypeDiv
	OpTypeMod
	OpTypeEq
)

func (t OperatorType) String() string {
	switch t {
	case OpTypeAdd:
		return "+"
	case OpTypeSub:
		return "-"
	case OpTypeMul:
		return "*"
	case OpTypeDiv:
		return "/"
	case OpTypeMod:
		return "%"
	case OpTypeEq:
		return "=="
	default:
		return "unknown"
	}
}

// OperatorExpr is the operator expression.
type OperatorExpr struct {
	Operator OperatorType
	Left     Expr
	Right    Expr
}

func (e *OperatorExpr) Type() ExprType {
	return ExprTypeOperator
}

func (e *OperatorExpr) String() string {
	return fmt.Sprintf("%s %s %s", e.Left, e.Operator, e.Right)
}

const ExprTypeUnary ExprType = "UnaryExpr"

type UnaryOperator rune

const (
	UnaryOpNegative UnaryOperator = '-'
	UnaryOpInverse  UnaryOperator = '!'
)

type UnaryExpr struct {
	Operator UnaryOperator
	Expr
}

func (e *UnaryExpr) Type() ExprType {
	return ExprTypeUnary
}

func (e *UnaryExpr) String() string {
	return fmt.Sprintf("%c%s", e.Operator, e.Expr)
}

const ExprTypeBracket ExprType = "BracketExpr"

// BracketExpr is the expression with brackets.
type BracketExpr struct {
	Expr
}

func (e *BracketExpr) Type() ExprType {
	return ExprTypeBracket
}

func (e *BracketExpr) String() string {
	if e.Expr == nil {
		return "()"
	}
	return fmt.Sprintf("(%s)", e.Expr)
}

const ExprTypeFunc ExprType = "FuncExpr"

// FuncExpr is the function expression.
type FuncExpr struct {
	Name   Expr
	Params []Expr
}

func (e *FuncExpr) Type() ExprType {
	return ExprTypeFunc
}

func (e *FuncExpr) String() string {
	paramStrs := make([]string, 0, len(e.Params))
	for _, p := range e.Params {
		paramStrs = append(paramStrs, p.String())
	}
	return fmt.Sprintf("%s(%s)", e.Name, strings.Join(paramStrs, ", "))
}

const ExprTypeVariable ExprType = "Variable"

// VariableExpr is the variable expression.
type VariableExpr struct {
	Name string
}

func (v *VariableExpr) Type() ExprType {
	return ExprTypeVariable
}

func (v *VariableExpr) String() string {
	return v.Name
}

const ExprTypeObjProp ExprType = "ObjVar"

// ObjPropExpr represent the notation of object property reference, like obj.prop.
type ObjPropExpr struct {
	Obj  Expr
	Prop string
}

func (o *ObjPropExpr) Type() ExprType {
	return ExprTypeObjProp
}

func (o *ObjPropExpr) String() string {
	return fmt.Sprintf("%s.%s", o.Obj, o.Prop)
}

const ExprTypeObjIndex ExprType = "ObjIndex"

// ObjIndexExpr represent the notation of object index reference, like obj[index].
type ObjIndexExpr struct {
	Obj   Expr
	Index Expr
}

func (o *ObjIndexExpr) Type() ExprType {
	return ExprTypeObjIndex
}

func (o *ObjIndexExpr) String() string {
	return fmt.Sprintf("%s[%s]", o.Obj, o.Index)
}

const ExprTypeValue ExprType = "Value"

// ValueExpr is the value expression.
type ValueExpr struct {
	Value any
}

func (v *ValueExpr) Type() ExprType {
	return ExprTypeValue
}

func (v *ValueExpr) String() string {
	switch val := v.Value.(type) {
	case string:
		return fmt.Sprintf(`"%s"`, val)
	case []rune:
		return fmt.Sprintf(`"%s"`, string(val))
	default:
		return fmt.Sprintf("%v", val)
	}
}
